using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Greycastle.RUBuildin.View
{
    /// <summary>The radial loader is a loader that will create a specified number of given elements around the loader center in a circle</summary>
    public class RadialLoader : UserControl
    {
        public static int DefaultNumberOfElements = 36;

        public static bool DefaultRotateElement = true;

        public static double DefaultAnimationOffsetScale = 1.0;

        public static readonly DependencyProperty NumberOfElementsProperty =
            DependencyProperty.RegisterAttached("NumberOfElements", typeof(int), typeof(RadialLoader), new PropertyMetadata(DefaultNumberOfElements, OnChange));

        public int NumberOfElements
        {
            get { return (int)GetValue(NumberOfElementsProperty); }
            set { SetValue(NumberOfElementsProperty, value); }
        }

        public static readonly DependencyProperty AnimationProperty =
            DependencyProperty.RegisterAttached("Animation", typeof(Storyboard), typeof(RadialLoader), new PropertyMetadata(default(Storyboard), OnChange));

        public Storyboard Animation
        {
            get { return (Storyboard)GetValue(AnimationProperty); }
            set { SetValue(AnimationProperty, value); }
        }

        public static readonly DependencyProperty RadiusProperty =
            DependencyProperty.RegisterAttached("Radius", typeof(double), typeof(RadialLoader), new PropertyMetadata(default(double), OnChange));

        public double Radius
        {
            get { return (double)GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }

        public static readonly DependencyProperty ElementTemplateProperty =
            DependencyProperty.RegisterAttached("ElementTemplate", typeof(DataTemplate), typeof(RadialLoader), new PropertyMetadata(default(DataTemplate), OnChange));

        public DataTemplate ElementTemplate
        {
            get { return (DataTemplate)GetValue(ElementTemplateProperty); }
            set { SetValue(ElementTemplateProperty, value); }
        }

        public static readonly DependencyProperty RotateElementProperty =
            DependencyProperty.RegisterAttached("RotateElement", typeof(bool), typeof(RadialLoader), new PropertyMetadata(DefaultRotateElement, OnChange));

        public bool RotateElement
        {
            get { return (bool)GetValue(RotateElementProperty); }
            set { SetValue(RotateElementProperty, value); }
        }

        public static readonly DependencyProperty AnimationOffsetScaleProperty =
            DependencyProperty.RegisterAttached("AnimationOffsetScale", typeof(double), typeof(RadialLoader), new PropertyMetadata(DefaultAnimationOffsetScale, OnChange));

        /// <summary>Describes to what ratio the animation should be offset</summary>
        /// <remarks>
        /// An animation offset of zero would make all animations start at the same point while 0.5 would offset the animation from start again halfway through.
        /// Anything over 1.0 would make the animation offset below the animation duration makin the last item not offset to before the animation end.
        /// </remarks>
        public double AnimationOffsetScale
        {
            get { return (double)GetValue(AnimationOffsetScaleProperty); }
            set { SetValue(AnimationOffsetScaleProperty, value); }
        }

        private readonly Canvas _canvas;
        private readonly Border _border;

        public RadialLoader()
        {
            _border = new Border();
            Content = _border;

            _canvas = new Canvas
            {
                Width = 0,
                Height = 0,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };

            _border.Child = _canvas;
        }

        private static void OnChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((RadialLoader)d).RebuildLoader();
        }

        public void RebuildLoader()
        {
            int objects = NumberOfElements;

            try
            {
                _canvas.Children.Clear();

                for (int i = 0; i < objects; ++i)
                {
                    double degree = i * (360.0 / objects);

                    var position = CalculatePositionInCircle(degree);

                    var element = new ContentPresenter
                    {
                        ContentTemplate = ElementTemplate,
                        Content = (i / (double)objects)
                    };

                    element.SetValue(Canvas.LeftProperty, position.X);
                    element.SetValue(Canvas.TopProperty, position.Y);

                    if (RotateElement)
                        element.RenderTransform = new RotateTransform(degree);

                    AnimateAtOffset(element, (double)i / objects);

                    _canvas.Children.Add(element);
                }
            }
            catch (Exception exc)
            {
                throw new Exception("Failed to construct loader: " + exc.Message, exc);
            }
        }

        private Point CalculatePositionInCircle(double degree)
        {
            double radians = ToRadians(degree);

            var normalizedPos = new Point(Math.Cos(radians), Math.Sin(radians));
            double x = Radius * normalizedPos.X;
            double y = Radius * normalizedPos.Y;

            return new Point(x, y);
        }

        private void AnimateAtOffset(FrameworkElement element, double animationOffset)
        {
            if (Animation == null)
                return;

            if (AnimationNotOffset)
            {
                Animation.Begin(element);
                return;
            }

            animationOffset = animationOffset * AnimationOffsetScale;
            var animationLength = Animation.Duration.TimeSpan.TotalSeconds;
            var offsetTime = TimeSpan.FromSeconds(animationOffset * animationLength);

            Animation.Begin(element, true); // true = can seek/control animation
            Animation.Seek(element, offsetTime, TimeSeekOrigin.BeginTime);
        }

        /// <summary>The animation will not be offset if the scale is zero effectively giving all items the same offset</summary>
        protected bool AnimationNotOffset
        {
            get { return Math.Abs(AnimationOffsetScale - 0) < 0.0001; }
        }

        private double ToRadians(double degrees)
        {
            return Math.PI * (degrees / 180.0);
        }
    }
}
